﻿//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: ObjectHolder_1.cs
//
//  Description: Holds a related persistent object.
// 
//--------------------------------------------------------------------------

using System;
using System.Reflection;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using Chili.Opf3.Query;
using System.Collections;

namespace Chili.Opf3.Relations
{
	/// <summary>
	/// Holds one single object that is connected with the parent object.
	/// </summary>
	/// <remarks>
	/// This class is used in a persistent class to hold one single object that is connected with 
	/// the parent object. When accessing the first time the 
	/// <see cref="Opf3.Relations.ObjectHolder{T}.InnerObject">InnerObject</see> property the related
	/// object is loaded. This is called delayed loading and reduces roundtrips to the storage.
	/// </remarks>
	/// <example>
	/// The following example shows how to use the ObjectHolder in your persistent objects. In the
	/// example each Call has one associated User.
	/// <code lang="CS">
	/// [Persistent("CALLS", PoolSize = 20)]
	/// public class Call
	/// {
	///		// The class contains an ObjectHolder and a <see cref="Opf3.Relations.RelationAttribute">RelationAttribute</see>
	///		// that specifies the relation between the parent object and the 
	///		// related objects.
	/// 	[Relation("UserId", "Id", SaveOrder = SaveOrders.ChildFirst)]
	/// 	private ObjectHolder&lt;User&gt; _user = new ObjectHolder&lt;User&gt;();
	/// 
	///		// The User property accesses the InnerObject property to get
	///		// the User object or to set a new one.
	/// 	public User User
	/// 	{
	/// 		get { return _user.InnerObject; }
	/// 		set { _user.InnerObject = value; }
	/// 	}
	///		
	///		// ... Other properties and code.
	/// }
	/// </code>
	/// </example>
	[Serializable]
	public sealed class ObjectHolder<T> : ObjectHolderBase
	{
		/// <summary>
		/// Creates a new instance of the <see cref="ObjectHolder{T}">ObjectHolder Class</see>.
		/// </summary>
		public ObjectHolder() : base()
		{
		}

		/// <summary>
		/// Creates a new instance of the <see cref="ObjectHolder{T}">ObjectHolder Class</see>.
		/// </summary>
		/// <param name="conditions">OPath that is used when retrieving the objects. For more information about OPath
		/// look at the <see cref="Opf3.Query.ObjectQuery{T}">ObjectQuery</see> class.</param>
		/// <param name="parameters">A list of parameters that are used in the OPath expression.</param>
		public ObjectHolder(string conditions, params object[] parameters)
			: base(new ObjectQuery<T>(conditions, parameters))
		{
		}

		/// <summary>
		/// Creates a new instance of the <see cref="ObjectHolder{T}">ObjectHolder Class</see>.
		/// </summary>
		/// <param name="query"><see cref="Opf3.Query.IQuery">IQuery</see> that is executed to load the associated 
		/// objects. This parameter allows you to use any kind of query without using the build in functionality.</param>
		public ObjectHolder(IQuery query) : base(query)
		{
		}

		/// <summary>
		/// Creates a new instance of the <see cref="ObjectHolder{T}">ObjectHolder Class</see>.
		/// </summary>
		/// <param name="info"></param>
		/// <param name="context"></param>
		private ObjectHolder(SerializationInfo info, StreamingContext context) : base(info, context)
		{
		}

		/// <summary>
		/// Event is fired before the data from the storage is used to populate an object.
		/// </summary>
		/// <remarks>
		/// This event is fired before the data from the storage is used to populate an object. It allows the user
		/// to specify another type for the row (item) that is populated with the data. It allows also to have 
		/// certain items ignored. This event can be used in certain scenarios of inheritance.
		/// </remarks>
		/// <example>
		/// The following example introduces to the use of the FecthingStorageRecord event. 
		/// <code lang="CS">
		/// [Persistent("CALLS", PoolSize = 20)]
		/// public class Call
		/// {
		///		// The class contains an ObjectHolder and a <see cref="Opf3.Relations.RelationAttribute">RelationAttribute</see>
		///		// that specifies the relation between the parent object and the 
		///		// related objects.
		/// 	[Relation("UserId", "Id", SaveOrder = SaveOrders.ChildFirst)]
		/// 	private ObjectHolder&lt;User&gt; _user = new ObjectHolder&lt;User&gt;();
		///    
		///     public Call()
		///     {
		///         _user.FetchingStorageRecord += PersistentTypeSelector(MySelection);
		///     }
		/// 
		///     private void MySelection(object sender, FetchingStorageRecordEventArgs e)
		///     {
		///        if (e.DataRecord["Type"] == "1")
		///        {
		///            // Switch the type of the persistent that is created to User1.
		///	           e.Type = typeof(User1);
		///        }
		///        else if ((bool)e.DataRecord["Ignore"])
		///        {
		///            // Tell the framework to ignore this item.
		///            e.Ignore = true;
		///        }
		///        // Have a persistent of type User created.
		///        e.Type = typeof(User);
		///     }
		///
		///		// The User property accesses the InnerObject property to get
		///		// the User object or to set a new one.
		/// 	public User User
		/// 	{
		/// 		get { return _user.InnerObject; }
		/// 		set { _user.InnerObject = value; }
		/// 	}
		///		
		///		// ... Other properties and code.
		/// }
		/// 
		/// // ...
		/// 
		/// Call call = context.GetObject&lt;Call&gt;("Name = {0}", "Call 1");
		/// User user = call.User;
		/// 
		/// if (user.GetType() == typeof(User1))
		///     Console.WriteLine("User1 found.");
		/// else
		///     Console.WriteLine("User found.");
		/// </code>
		/// </example>
		public event PersistentTypeSelector FetchingStorageRecord;

		/// <summary>
		/// Invokes the FetchingStorageRecord event.
		/// </summary>
		private void OnFetchingStorageRecord(object sender, FetchingStorageRecordEventArgs e)
		{
			if (FetchingStorageRecord != null)
				FetchingStorageRecord(this, e);
		}

		/// <summary>
		/// Gets and sets the persistent object that is related to the parent object.
		/// </summary>
		/// <value>Returns the related persistent object.</value>
		[XmlIgnore]
		public T Value
		{
			get { return this.InnerObject; }
			set { this.InnerObject = value; }
		}

		/// <summary>
		/// Depreciated: Use the Value property instead!
		/// Gets and sets the persistent object that is related to the parent object.
		/// </summary>
		/// <value>Returns the related persistent object.</value>
		[XmlIgnore]
		public T InnerObject
		{
			get
			{
				if (!AutomaticLoad)
					return (T)this.ManagedObject;

				// Get the object if not loaded.
				if (!IsLoaded)
				{
					InnerLoad(this.ObjectContext);
					
					Tracing.Current.TraceStorageIO("Loaded persistent's related objects from storage.");
					// Set the ObjectSetHolder as loaded.
					IsLoaded = true;
				}

				return (T)this.ManagedObject;
			}
			set 
			{
				this.ManagedObject = value;
				// Set the ObjectHolder as loaded if not null is set.
				if (value != null)
					IsLoaded = true;
			}
		}

		/// <summary>
		/// Loads the related object(s) from the storage.
		/// </summary>
		protected override void InnerLoad(ObjectContext context)
		{
			// Check if the parent implements the IRelationsPopulateHelper.
			IRelationsPopulateHelper helper = this.Parent as IRelationsPopulateHelper;
			// Check if the relation is handled by the parent.
			if (helper != null)
			{
				// Create the relation information.
				RelationInformation info = new RelationInformation(this.Parent, typeof(T), this.RelationAttribute, context);
				if (helper.RelationIsHandled(this.RelationAttribute.Name, info))
				{
					// Get the object from the parent if requested.
					this.ManagedObject = helper.GetRelatedObject(this.RelationAttribute.Name, info);
					return;
				}
			}

			// Creates the query.
			IQuery query = ObjectHolderUtility.PrepareQuery<T>(this.RelationAttribute, this.Query, this.Parent);
			if (query != null && context != null)
			{
				// This check is here for remoting. On some privilege level the delegate can't be passed to the server.
				// If the delegate is null it is not passed ot the server.
				if (FetchingStorageRecord == null)
					this.ManagedObject = context.GetObject<T>(query);
				else
					this.ManagedObject = context.GetObject<T>(new PersistentTypeSelector(OnFetchingStorageRecord), query);
			}
		}

		/// <summary>
		/// This method is invoked to persist the changes on the objects in the ObjectHolder.
		/// </summary>
		/// <param name="context"><see cref="Opf3.ObjectContext">ObjectContext</see> used to persist 
		/// the changes. It is the same <see cref="Opf3.ObjectContext">ObjectContext</see> as used by the
		/// parent object.</param>
		/// <param name="persistDepth">Specifies the depth of the loaded object graph that is persisted.</param>
		internal override void PersistChanges(ObjectContext context, PersistDepths persistDepth)
		{
			this.ObjectContext = context;

			// Persist the object using the internal context.
			if (this.ManagedObject != null)
			{
				if (PersistDepths.Flat != persistDepth)
				{
					if (this.RelationAttribute.WeakPersistent != null)
						throw new NotImplementedException("Many-to-Many relation can't be saved with ObjectHolder. Use ObjectSetHolder instead.");

					// Set to flat since there is only one level left.
					if (persistDepth == PersistDepths.FirstLevel)
						this.ObjectContext.PersistChanges((T)this.ManagedObject, PersistDepths.Flat);
					else
						this.ObjectContext.PersistChanges((T)this.ManagedObject, PersistDepths.FullObjectGraph);
				}

				// Skip if object is marked for deletion.
				ObjectInfo objectInfo = context.GetObjectInfo(this.Parent);
				if (objectInfo.MarkForDeletion)
					return;

				// Update the connected property.
				UpdateConnectedProperty((T)this.ManagedObject);
			}
		}

		/// <summary>
		/// This method connects the foreign-key properties in the child persistent objects with the identifier
		/// properties of the parent object.
		/// </summary>
		internal override void ConnectPropertiesInChildObjects()
		{
			if (this.ManagedObject != null)
				UpdateConnectedProperty(this.ManagedObject);
		}

		/// <summary>
		/// This method is invoked to check if any of the ObjectHolder's objects have been modified.
		/// </summary>
		/// <param name="context"><see cref="Opf3.ObjectContext">ObjectContext</see> used to check for modified 
		/// objects. It is the same <see cref="Opf3.ObjectContext">ObjectContext</see> as used by the
		/// parent object.</param>
		/// <returns>True if an object has been modified; false othewise.</returns>
		internal override bool HasModifiedObjects(ObjectContext context)
		{
			if (this.ManagedObject == null)
				return false;

			return context.IsModified(this.ManagedObject, true);
		}

		/// <summary>
		/// This method is invoked to check if the <see cref="ObjectHolder" /> has removed objects.
		/// </summary>
		/// <returns>True if an object has been removed; false otherwise</returns>
		internal override bool HasRemovedObjects()
		{
			return false;
		}

		/// <summary>
		/// <para>
		/// Invalidates the current instance. The next request of the InnerObject property reloads all the 
		/// related objects. This is only true if the automatic load of the related objects has been enabled. 
		/// Otherwise the reload method needs to be invoked (after calling Invalidate) to do a manual reload.
		/// </para>
		/// <para>
		/// The call of this method removes the objects also from the identity map if one is in place. This makes
		/// sure that the objects are really reloaded from the storage and not from the identity map again.
		/// </para>
		/// </summary>
		public override void Invalidate()
		{
			// set the inner object back to null.
			this.ManagedObject = default(T);
			base.Invalidate();
		}

		/// <summary>
		/// Gets the enumerator for the given loaded related data. This method doesn't load
		/// data from the storage but rather returns the currently loaded data.
		/// </summary>
		internal override IEnumerable GetRawEnumerator()
		{
			if (this.ManagedObject == null)
				yield break;

			yield return this.ManagedObject;
		}
	}
}
